<?php
/**
 * Classe de calcul d'URL pour entête Location:
 *
 * ou pour calculer l'adresse absolue d'une autre page, pour modifier les
 * paramètres get de la page actuelle, etc.
 *
 * Définir une constante HTTP_URL_BASE avec une URL de base absolue à utiliser
 * dans le cas d'installation de l'application cible derrière un CSS ou un proxy
 *
 * @package    HTTP
 * @author     Philippe MARASSE <philippe.marasse@ac-poitiers.fr>
 * @copyright  2006-2007 D.I.R. Académie de Poitiers
 * @license    http://www.opensource.org/licenses/gpl-license.php
 * @version    CVS: $Id: class.dirapHttpUrl.inc.php,v 1.2 2007/06/19 15:32:20 philippe Exp $
 *
 */

class dirapHttpUrl {

    /**
     * Constante d'expression régulière de validation d'URL
     */
    const URL_REGEX = '_^[a-z]+://[a-z.-]+[a-z]{2,}(:[0-9]+)?/_';
    
    /**
     * Recompose une URL en fonction des paramètres passés. 
     * 
     * Il existe un problème pour retrouver le protocole correct quand on 
     * utilise un boitier CSS pour gérer les accès SSL, dans ce cas, utiliser 
     * la constante HTTP_URL_BASE
     *
     * @access public
     * @param string $pagename nom de la nouvelle page, rien ou false pour soi-même
     * @param array  $oargs arguments GET à intégrer dans l'URL, par exemple $_GET
     * @return string URL recomposée prête à être utilisée par un header Location:
     * @throws Exception si la constante ne contient pas une URL valide ou 
     *    si l'URL calculée est invalide
     */
    public static function getUrl($pagename = false, $oargs = array()) {

        $urlelts = array();
        if (defined('HTTP_URL_BASE')) {
             if (($urlelts = parse_url(HTTP_URL_BASE)) !== false ) {
                 $proto = strtolower($urlelts['scheme']).'://';
                 $srvport = isset($urlelts['port']) ? $urlelts['port'] : ( ($proto == 'http://') ? 80 : 443);
             } else {
                 throw new Exception(__METHOD__ .' - l\'URL fournie est invalide');
             }
        } else {
         
            if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) {
                $proto   = 'https://';
                $srvport = 443;
            } else {
                $proto   = 'http://';
                $srvport = 80;
            }
        }

        // HTTP_HOST existant, non vide et pas d'hôte défini
        if (! isset($urlelts['host']) && isset($_SERVER['HTTP_HOST']) && 
            ! empty($_SERVER['HTTP_HOST'])) {

            $url = $proto.$_SERVER['HTTP_HOST'];

        } else {
            // Test pour savoir si srvname a été passé, auquel cas on va intégrer
            // l'éventuel user et passe
            if (isset($urlelts['host'])) {
                $url  = $proto;
                $url .= (isset($urlelts['user'])) ? $urlelts['user'] : '';
                $url .= (isset($urlelts['pass'])) ? $urlelts['pass'] : '';
                $url .= $urlelts['host'];
            } else {
                $url=$proto.$_SERVER['SERVER_NAME'];
            }

        

        // Ajout de l'URL d'appel http + serveur + port (si pas 80) + self + args
        if (($_SERVER['SERVER_PORT'] != $srvport) && ! defined('HTTP_URL_BASE')) {
            $url .= ':'.$_SERVER['SERVER_PORT'];
        } else {
            $url .= (isset($urlelts['port'])) ? $urlelts['port'] : '';
        }
        }
        // Ajoute le chemin fourni
        $url .= (isset($urlelts['path'])) ? $urlelts['path'] : '';
        
        // On note si il faudra ou non ajouter un slash avant le chemin
        $slashfinal = ($url{strlen($url) - 1} == '/');
            
        // On calcule ensuite le chemin de la page
        if ($pagename === FALSE) {
            $path = $_SERVER['SCRIPT_NAME'];
        } else {
            // Evite le bug du double slash
            if (dirname($_SERVER['SCRIPT_NAME']) == '/') {
                $path = '/'.$pagename;
            } else {
                $path = dirname($_SERVER['SCRIPT_NAME']).'/'.$pagename;
            }
        }
        // On supprime l'éventuel préfixe avant de l'associer à l'URL
        if (defined('HTTP_URL_LOCAL_PREFIX')) {
            $prefixe = addcslashes(HTTP_URL_LOCAL_PREFIX,'/');
            if ($prefixe{1} != '/') { $prefixe = '\/'.$prefixe; }
            $path = preg_replace('/^'.$prefixe.'/','',$path);
        }
        
        // On règle le problème éventuel de double slash entre chemin et préfixe.
        $slashinitial = ($path{0} == '/');
        if ($slashfinal && $slashinitial) {
            // Il faut en virer l'un des deux
            $url .= substr($path, 1, strlen($path) - 1);
        } elseif ($slashfinal || $slashinitial) {
            // Rien à faire
            $url .= $path;
        } else {
            // Il faut en ajouter un
            $url .= '/'.$path;
        }
        
        // On va supprimer les '..' éventuels itérativement
        $valid = preg_match(self::URL_REGEX,$url);
        if (! $valid) {
            throw new Exception (__METHOD__. ' URL: '.$url.' Invalide');
        }

        do {
            // motif de base : '`[^/]+/\.\./`'
            $urlred = preg_replace ('`[^/]+/\.\./`','',$url,1);
            $estreduite = strlen($urlred) < strlen($url);
            $url = $urlred;
            $valid = preg_match(self::URL_REGEX,$urlred);
        } while ($estreduite && ($valid > 0));

        if (! $valid) {
            throw new Exception (__METHOD__. ' URL réduide '.$urlred.' Invalide');
        }
        
        if (!empty($oargs)) { $urlred .= '?'.http_build_query($oargs); }
        return($urlred);
    }

    /**
     * Positionne un header Location en fonction des paramètres fournis
     *
     * @access public
     * @param string $pagename nom de la nouvelle page, rien ou false pour soi-même
     * @param array $oargs arguments GET à intégrer dans l'URL, par exemple $_GET
     * @param string $srvname nom du nouveau serveur (vide pour ne pas changer)
     * @return boolean Vrai parce que l'opération réussit
     */
    public static function redirectTo($pagename = FALSE, $oargs = array()) {
        header('Location: '.self::getUrl($pagename,$oargs));
        return true;
    }
}

?>